package global;

import iterator.*;

import va_btree.NNBTIndexScan;
import va_btree.RSBTIndexScan;
import va_file.NNIndexScan;
import va_file.RSIndexScan;

//Abstract class for all the query types to be based off of

abstract class Query {
	
	public enum queryType{
		NN,
		Range,
		NLJ,
		Sort,
		DJOIN1,
		DJOIN2;
	};
	
	public queryType type;
	public AttrType[] outTypes;
	public int[] outFlds;
    public String indexOption;
	
	// Function that retrieves the iterator to iterate through the final tuples
	public abstract Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception;
	
}

//Provide an abstraction for the outer relation query in an indexed NLJ; done to reduce code duplication
class DJOINOuterQuery extends Query {
	
	public RangeQuery rangeQuery;
	public NNQuery nnQuery;
	
	public DJOINOuterQuery(RangeQuery rangeQuery){
		this.rangeQuery = rangeQuery;
		nnQuery = null;}
	
	public DJOINOuterQuery(NNQuery nnQuery){
		this.nnQuery = nnQuery;
		rangeQuery = null;}
	
	public  Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception{
		if(rangeQuery != null) return rangeQuery.getIterator(relName1, relName2, useIndex);
		else return nnQuery.getIterator(relName1, relName2, useIndex);}
	
	public int getFieldNum(){
		if(rangeQuery != null) return rangeQuery.fieldNumber;
		else return nnQuery.fieldNumber;}
	
	public int[] getOutFields(){
		if(rangeQuery != null) return rangeQuery.outFlds;
		else return nnQuery.outFlds;}
	
}


class DJOIN extends Query {
	
	public DJOINOuterQuery outerQuery;
	public int distance;
	public int QA2; //field number for vector in second string
	public int[] innerFields; //fields that need to be projected on inner relation
	public IndexType indexType;
	public int noOfBits;
	
	public DJOIN(DJOINOuterQuery outerQuery, IndexType indexType, int QA2, int[] innerFields, int distance, int noOfBits) {
		this.outerQuery = outerQuery;
		this.distance = distance;
		this.indexType = indexType;
		this.QA2 = QA2;
		this.innerFields = innerFields;
		this.noOfBits = noOfBits;
	}
	
	public Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception {
		
		Iterator outerIterator = outerQuery.getIterator(relName1, relName2, useIndex);

		// build expression for join
	    CondExpr[] expr = new CondExpr[2];
	    expr[0] = new CondExpr();
	    expr[0].next  = null;
	    expr[0].op    = new AttrOperator(AttrOperator.aopLT);
	    expr[0].type1 = new AttrType(AttrType.attrSymbol);
	    expr[0].type2 = new AttrType(AttrType.attrSymbol);
	    expr[0].operand1.symbol = new FldSpec (new RelSpec(RelSpec.outer),outerQuery.getFieldNum());
	    expr[0].operand2.symbol = new FldSpec (new RelSpec(RelSpec.innerRel),QA2);
	    expr[0].distance = distance;
	    expr[1] = null;

	    AttrType[] types1 = QueryCommand.schema1.attrType;
	    short[] strSizes1 = QueryCommand.schema1.attrSize;
	    
	    // build the output fields as the union of output fields from nnQuery and rangeQuery
	    FldSpec[] projlist = new FldSpec[innerFields.length + outerQuery.getOutFields().length];
	    this.outTypes = new AttrType[projlist.length];
	    this.outFlds = new int[projlist.length];
	    for (int i=0; i<outerQuery.getOutFields().length; i++)
	    {
	    	int fldNum = outerQuery.getOutFields()[i];
	    	projlist[i] = new FldSpec(new RelSpec(RelSpec.outer), fldNum);
	    	outTypes[i] = new AttrType(types1[fldNum-1].attrType);
	    	outFlds[i] = i+1;
	    }
	    
	    AttrType[] types2 = QueryCommand.schema2.attrType;
	    short[] strSizes2 = QueryCommand.schema2.attrSize;
	    
	    int offset = outerQuery.getOutFields().length;
	    for (int i=0; i<innerFields.length; i++)
	    {
	    	int fldNum = innerFields[i];
	    	projlist[i+offset] = new FldSpec(new RelSpec(RelSpec.innerRel), fldNum);
	    	outTypes[i+offset] = new AttrType(types1[fldNum-1].attrType);
	    	outFlds[i+offset] = i+offset+1;
	    }
	    
	    String idxType;
	    String indexName = null;
	    switch(indexType.indexType)
	    {
	    	case IndexType.VA_BT_Index:
	    		if(useIndex) indexName = IDXSchema.getIDXFileName(relName2, this.QA2, "B", this.noOfBits);
	    	 break;
	    	
	    	case IndexType.VA_Index:
	    		if(useIndex) indexName = IDXSchema.getIDXFileName(relName2, this.QA2, "H", this.noOfBits);
		    break;
		    	
	    	default:
	    		break;
	    	
	    }
	    
		return new IndexNLJ( types1, types1.length, strSizes1, types2, types2.length, strSizes2, CommandLine.SORTPGNUM, 
				outerIterator, relName2, indexType, indexName, expr, null, projlist, projlist.length);    
	}
	
}

class NLJQuery extends Query {
	
	public NNQuery nnQuery;
	public RangeQuery rangeQuery;
	public int distance;
	
	public NLJQuery(NNQuery nnQuery, RangeQuery rangeQuery, int distance) {
		this.nnQuery = nnQuery;
		this.rangeQuery = rangeQuery;
		this.distance = distance;
	}
	
	public Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception {

		rangeQuery.forNLJ = true;
		
		Sort nnIterator = (Sort) nnQuery.getIterator(relName1, null, useIndex);
		Iterator rangeIterator = rangeQuery.getIterator(relName2, null, useIndex);

		// build expression for join
	    CondExpr[] expr = new CondExpr[2];
	    expr[0] = new CondExpr();
	    expr[0].next  = null;
	    expr[0].op    = new AttrOperator(AttrOperator.aopLT);
	    expr[0].type1 = new AttrType(AttrType.attrSymbol);
	    expr[0].type2 = new AttrType(AttrType.attrSymbol);
	    expr[0].operand1.symbol = new FldSpec (new RelSpec(RelSpec.outer),rangeQuery.fieldNumber);
	    expr[0].operand2.symbol = new FldSpec (new RelSpec(RelSpec.innerRel),nnQuery.fieldNumber);
	    expr[0].distance = distance;
	    expr[1] = null;

	    AttrType[] types = QueryCommand.schema1.attrType;
	    short[] strSizes = QueryCommand.schema1.attrSize;
	    
	    // build the output fields as the union of output fields from nnQuery and rangeQuery
	    FldSpec[] projlist = new FldSpec[nnQuery.outFlds.length + rangeQuery.outFlds.length];
	    this.outTypes = new AttrType[projlist.length];
	    this.outFlds = new int[projlist.length];
	    for (int i=0; i<rangeQuery.outFlds.length; i++)
	    {
	    	int fldNum = rangeQuery.outFlds[i];
	    	projlist[i] = new FldSpec(new RelSpec(RelSpec.outer), fldNum);
	    	outTypes[i] = new AttrType(types[fldNum-1].attrType);
	    	outFlds[i] = i+1;
	    }
	    int offset = rangeQuery.outFlds.length;
	    for (int i=0; i<nnQuery.outFlds.length; i++)
	    {
	    	int fldNum = nnQuery.outFlds[i];
	    	projlist[i+offset] = new FldSpec(new RelSpec(RelSpec.innerRel), fldNum);
	    	outTypes[i+offset] = new AttrType(types[fldNum-1].attrType);
	    	outFlds[i+offset] = i+offset+1;
	    }
	    
		return new NestedLoopsJoins( types, strSizes, types, strSizes, CommandLine.SORTPGNUM, 
				rangeIterator, nnIterator, expr, null, projlist);    
	}
	
}

class NNQuery extends Query {
	
	public int fieldNumber;
	public Vector100Dtype target;
	public int k;
    public int bits;
	
	public NNQuery(int fieldNumber, Vector100Dtype target, int k, int[] outputFlds, String indexOption, int bits) {
		this.fieldNumber = fieldNumber;
		this.target = target;
		this.k = k;
		this.outFlds = outputFlds;
		this.outTypes = QueryCommand.schema1.attrType;
        this.indexOption = indexOption;
        this.bits = bits;
	}
	
	public Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception {
		Iterator scan = null;
        Schema schema = Schema.getSchema(relName1);
		// MY: need FldSpec[] for all attribs for Sort and NLJ to work correctly. Will need to
		// display selected output fields with printing tuple.
        // Perform different operations depending on the index type requested
        FldSpec[] projlist = CommandLine.getFldSpecForAll(schema.attrType);
        if (indexOption.equals("H")) {
			//To get the parameters AttrType[] types, short[] str_sizes, and int noInFields, we need to look at the database then pass that info to this class's constructor
			//We can leave the CondExpr[] parameter null since we don't need to filter based on conditions
            String idxFileName = IDXSchema.getIDXFileName(relName1, this.fieldNumber, "H", this.bits);
            IDXSchema schemaIdx = IDXSchema.GetIndex(idxFileName);
            if (schemaIdx == null) {
                //throw new Exception("The specified index schema does not exist!");
            	System.out.println("The specified index does not exist, executing with no index.");
            }
            else {
            	scan = new NNIndexScan(new IndexType(IndexType.VA_Index), relName1, idxFileName, schema.attrType, schema.attrSize, schema.attrType.length, projlist.length, projlist, null, this.fieldNumber, this.target, this.k, schemaIdx.Bits);
            }
		} else if (indexOption.equals("B")) {
            //To get the parameters AttrType[] types, short[] str_sizes, and int noInFields, we need to look at the database then pass that info to this class's constructor
            //We can leave the CondExpr[] parameter null since we don't need to filter based on conditions
            String idxFileName = IDXSchema.getIDXFileName(relName1, this.fieldNumber, "B", this.bits);
            IDXSchema schemaIdx = IDXSchema.GetIndex(idxFileName);
            if (schemaIdx == null) {
                //throw new Exception("The specified index schema does not exist!");
            	System.out.println("The specified index does not exist, executing with no index.");
            }
            else {
            	scan = new NNBTIndexScan(new IndexType(IndexType.VA_Index), relName1, idxFileName, schema.attrType, schema.attrSize, schema.attrType.length, projlist.length, projlist, null, this.fieldNumber, this.target, this.k, schemaIdx.Bits);
            }
        } 
		if (scan==null) {
			scan = new FileScan(relName1, schema.attrType, schema.attrSize, (short)schema.attrType.length, projlist.length, projlist, null);
		}
		return new Sort(schema.attrType, (short)schema.attrType.length, schema.attrSize, scan, this.fieldNumber, new TupleOrder(TupleOrder.Ascending), 200, CommandLine.SORTPGNUM, target, k);
	}
	
}

class RangeQuery extends Query {
	
	public int fieldNumber;
	public Vector100Dtype target;
	public int distance;
	public boolean forNLJ;
    public int bits;
	
	public RangeQuery(int fieldNumber, Vector100Dtype target, int distance, int[] outputFlds, String indexOption, int bits) {
		this.fieldNumber = fieldNumber;
		this.target = target;
		this.distance = distance;
		this.outFlds = outputFlds;
		this.outTypes = QueryCommand.schema1.attrType;
        this.indexOption = indexOption;
        this.bits = bits;
	}
	
	public Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception {
		Iterator scan = null;
		Schema schema = Schema.getSchema(relName1);

		FldSpec[] projlist = CommandLine.getFldSpecForAll(schema.attrType);

        // Perform different operations depending on the index type requested
		if (indexOption.equals("H")) {
			//To get the parameters AttrType[] types, short[] str_sizes, and int noInFields, we need to look at the database then pass that info to this class's constructor
			//We can leave the CondExpr[] parameter null since we don't need to filter based on conditions
            String idxFileName = IDXSchema.getIDXFileName(relName1, this.fieldNumber, "H", this.bits);
            IDXSchema schemaIdx = IDXSchema.GetIndex(idxFileName);
            if (schemaIdx == null) {
                //throw new Exception("The specified index schema does not exist!");
            	System.out.println("The specified index does not exist, executing with no index.");
            }
            else {
            	scan = new RSIndexScan(new IndexType(IndexType.VA_Index), relName1, idxFileName, schema.attrType, schema.attrSize, schema.attrType.length, projlist.length, projlist, null, fieldNumber, target, distance, schemaIdx.Bits);
            }
		} else if (indexOption.equals("B")) {
            //To get the parameters AttrType[] types, short[] str_sizes, and int noInFields, we need to look at the database then pass that info to this class's constructor
            //We can leave the CondExpr[] parameter null since we don't need to filter based on conditions
            String idxFileName = IDXSchema.getIDXFileName(relName1, this.fieldNumber, "B", this.bits);
            IDXSchema schemaIdx = IDXSchema.GetIndex(idxFileName);
            if (schemaIdx == null) {
                //throw new Exception("The specified index schema does not exist!");
            	System.out.println("The specified index does not exist, executing with no index.");
            }
            else {
            	scan = new RSBTIndexScan(new IndexType(IndexType.VA_Index), relName1, idxFileName, schema.attrType, schema.attrSize, schema.attrType.length, projlist.length, projlist, null, fieldNumber, target, distance, schemaIdx.Bits);
            }
        } 
		if (scan==null) {
			scan = new RangeVectorScan(relName1, schema.attrType, schema.attrSize, (short)schema.attrType.length, projlist.length, projlist, null, fieldNumber, target, distance);
		}
		return scan;
	}
}

class SortQuery extends Query {
	public int FieldNumber;
	public Vector100Dtype Target;
	
	public SortQuery(int fieldNumber, Vector100Dtype target, int[] outputFlds) {
		this.FieldNumber = fieldNumber;
		this.Target = target;
		this.outFlds = outputFlds;
		this.outTypes = QueryCommand.schema1.attrType;
	}

	public Iterator getIterator(String relName1, String relName2, boolean useIndex) throws Exception {
        Schema schema = Schema.getSchema(relName1);
        FldSpec[] projlist = CommandLine.getFldSpecForAll(schema.attrType);
		Iterator scan = new FileScan(relName1, schema.attrType, schema.attrSize, (short)schema.attrType.length, projlist.length, projlist, null);
		return new Sort(schema.attrType, (short)schema.attrType.length, schema.attrSize, scan, this.FieldNumber, new TupleOrder(TupleOrder.Ascending), 200, CommandLine.SORTPGNUM, Target, 0);
	}
}
	